iT邦幫忙

2023 iThome 鐵人賽

DAY 24
0
自我挑戰組

Lex & Yacc 學習筆記系列 第 24

[Day24] Lex 進階 - yywrap 應用

  • 分享至 

  • xImage
  •  

本篇內容

  • 介紹讀取多份檔案的程式架構
  • 範例 - 文字計數器

介紹

我們在先前的範例中,程式在讀取完單一檔案便結束了。如果要讀取多份檔案的話,程式就要不斷的呼叫 Parser。然而,這樣會造成前面的文件資訊被直接清除。如果要連續的輸入文件,其實可以好好利用之前提到的 yywrap function。這個function在 yylex 結束前被呼叫,若是回傳 1 ,代表文件讀取完畢,而回傳 0 則代表尚有文件沒有讀取。因此,我們可以利用這個特性,來製作一個可以連續讀取多份文件的程式。

範例 - 文字計數器2

說明

請實作一份可以連續讀取多份文件的文字計數器,並顯示每份文件的行數、字數與字元數。最後,顯示總計的行數、字數與字元數。輸入的文件寫在執行檔後,範例如下:

./main.exe file1.txt file2.txt ...

程式實作

  • 單一文件的計數變數與規則與之前相同
  • 在Subroutine區塊,我們需要定義
    • 總計的行數、字數與字元數
    • 記錄文件名稱的array
    • 記錄文件數量
    • 記錄目前讀取的文件
int ncharTotal = 0;
int nwordTotal = 0;
int nlineTotal = 0;

char **fileList;
int currentFile = 0;
int nFiles;
  • 在main function中,我們可以從 argument 數量得知檔案數量
    • 若 argc 為1,則沒有檔案,提早結束。
    • 若 argc 為2,則僅有一個檔案,將 yyin 指向該檔案。
    • 若 argc 大於3,則有複數個檔案,透過 yywrap 將 yyin 指向特定的檔案。
    • 開始讀檔
    • 若只有一個檔案,印出結果並結束主程式
    • 若有複數個檔案,印出最終結果並結束主程式
int main(int argc, char **argv) {
    FILE *file;
    fileList = argv + 1;
    nFiles = argc - 1;

    if (argc == 1) {
        printf("No input files!\n");
        return -1;
    }

    if (argc == 2) {
        currentFile = 1;
        file = fopen(argv[1], "r");
        if (!file) {
            fprintf(stderr,"could not open %s\n",argv[1]);
            return -1;
        }
        yyin = file;
    }

    if (argc > 2) yywrap();

    yylex();

    if (argc > 2) {
        printf("%s result: line %d, word %d, char %d\n", fileList[currentFile - 1], nline, nword, nchar);
        ncharTotal += nchar;
        nwordTotal += nword;
        nlineTotal += nline;
        printf("total: line %d, word %d, char %d\n", nlineTotal, nwordTotal, ncharTotal);
    } else {
        printf("A result: line %d, word %d, char %d\n", nline, nword, nchar);
    }
        
    return 0;
}

  • 在 yywrap 程式中,在複數個檔案的情況下,要將每次的結果更新到最終結果。
    • 若還有檔案未讀取,要將 yyin 指向下一個檔案,並回傳 0
    • 若所有檔案皆已讀取,則回傳 1。
int yywrap() {
    FILE *file = NULL;
    if ((currentFile != 0) && (nFiles > 1) && (currentFile < nFiles)) {
        printf("%s result: line %d, word %d, char %d\n", fileList[currentFile - 1], nline, nword, nchar);
        ncharTotal += nchar;
        nwordTotal += nword;
        nlineTotal += nline;
        nchar = nword = nline = 0;
        fclose (yyin);
    }
    while (fileList[currentFile] != nullptr) {
        file = fopen(fileList[currentFile++], "r");
        if (file != NULL) {
            yyin = file;
            break;
        }
        fprintf(stderr, "could not open %s\n", fileList[currentFile - 1]);
    }
    return (file ? 0 : 1);
}

完整程式碼

%{
    int yylex(void);
    int nchar, nword, nline;
%}

word    [^ \t\n]+
eol     \n

%%

{eol}         { nline++; nchar++; }
{word}        { nword++, nchar += yyleng; }
.           { nchar++; } 

%%

int ncharTotal = 0;
int nwordTotal = 0;
int nlineTotal = 0;

char **fileList;
int currentFile = 0;
int nFiles;

int main(int argc, char **argv) {
    FILE *file;
    fileList = argv + 1;
    nFiles = argc - 1;

    if (argc == 1) {
        printf("No input files!\n");
        return -1;
    }

    if (argc == 2) {
        currentFile = 1;
        file = fopen(argv[1], "r");
        if (!file) {
            fprintf(stderr,"could not open %s\n",argv[1]);
            return -1;
        }
        yyin = file;
    }

    if (argc > 2) yywrap();

    yylex();

    if (argc > 2) {
        printf("%s result: line %d, word %d, char %d\n", fileList[currentFile - 1], nline, nword, nchar);
        ncharTotal += nchar;
        nwordTotal += nword;
        nlineTotal += nline;
        printf("total: line %d, word %d, char %d\n", nlineTotal, nwordTotal, ncharTotal);
    } else {
        printf("A result: line %d, word %d, char %d\n", nline, nword, nchar);
    }
        
    return 0;
}

int yywrap() {
    FILE *file = NULL;
    if ((currentFile != 0) && (nFiles > 1) && (currentFile < nFiles)) {
        printf("%s result: line %d, word %d, char %d\n", fileList[currentFile - 1], nline, nword, nchar);
        ncharTotal += nchar;
        nwordTotal += nword;
        nlineTotal += nline;
        nchar = nword = nline = 0;
        fclose (yyin);
    }
    while (fileList[currentFile] != nullptr) {
        file = fopen(fileList[currentFile++], "r");
        if (file != NULL) {
            yyin = file;
            break;
        }
        fprintf(stderr, "could not open %s\n", fileList[currentFile - 1]);
    }
    return (file ? 0 : 1);
}

執行結果

輸入內容

  • file1.txt
Hello, World!
I am a software engineer.
I like lex and yacc.
  • file2.txt
Hello, BarleyTea!
My name is BlackTea.
I am also a software engineer.
I am learning lex and yacc.

輸出結果

file.txt result: line 3, word 12, char 60
file2.txt result: line 4, word 18, char 97
total: line 7, word 30, char 157

結語

雖然看起來有點複雜,但只要掌握yywrap的基本原則,就可以連續讀取多個檔案囉~

參考資料

  • Levine, John R., Tony Mason and Doug Brown [1992]. Lex & Yacc. O’Reilly & Associates, Inc. Sebastopol, California.
  • Tom Niemann. Lex & Yacc

上一篇
[Day23] Lex 進階 - State 其他用法
下一篇
[Day25] Yacc 進階 - 空白規則
系列文
Lex & Yacc 學習筆記30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言